package com.gitee.l0km.codegen.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.Service;
import com.gitee.l0km.codegen.annotations.ServicePort;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.InvalidNameException;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.MethodException;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.codegen.base.WebServiceClassConstants;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.codegen.base.generator.GeneratorUtils;
import com.gitee.l0km.com4j.base.Assert;

public class WebServiceClass<T> extends NewSourceInfoAbstract<T> implements WebServiceClassConstants {
	private static final Logger logger = LoggerFactory.getLogger(WebServiceClass.class);

	private ServiceInfo serviceInfo;

	public WebServiceClass(Class<T> interfaceClass, Class<? extends T> refClass) {
		super(interfaceClass, refClass, null);
		Assert.notNull(refClass, "refClass");
	}

	@Override
	public boolean compile() {
		boolean compileOk = false;
		try {
			if (super.compile()) {
				Service service = AnnotationUtils.getServiceAnnotation(interfaceClass);
				if (null != service)
					addImportedClass(service.genericTypeClasses());
				addImportedClass(refClass);
				serviceInfo = new ServiceInfo(service);
				for (Method method : methodsNeedGenerated) {
					compile(method);
				}
				compileOk = true;
			}
		} catch (AnnotationException e) {
			logger.error(e.toString());
		} catch (AnnotationRuntimeException e) {
			logger.error(e.toString());
		}
		return compileOk;
	}

	private final void compile(Method method) throws InvalidNameException {
		ServicePort servicePort = method.getAnnotation(ServicePort.class);
		if (null != servicePort) {
			if (!(servicePort.suffix().isEmpty() || servicePort.suffix().matches(AnnotationUtils.PATTERN_NOT_BLANK)))
				throw new InvalidNameException(
						String.format("the prefix of annotation [%s] in method %s of %s must not have space char ",
								ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
										.getSimpleName()));
			Set<String> nameSet=CodeGenUtils.toSetIfnotDup(servicePort.genericParam());
			if(null==nameSet)
				throw new InvalidNameException(
						String.format("the genericParam of annotation [%s] in method %s of %s must not have duplicated element",
								ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
										.getSimpleName()));
			for (String gp : nameSet) {
				if (gp.isEmpty())
					throw new InvalidNameException(String.format(
							"the genericParam of annotation [%s] in method %s of %s must not be empty element ",
							ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
									.getSimpleName()));
				if (!gp.matches(AnnotationUtils.PATTERN_NOT_BLANK))
					throw new InvalidNameException(String.format(
							"the prefix of annotation [%s] in method %s of %s must not have space char ",
							ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
									.getSimpleName()));
				Parameter parameter = method.getParameter(gp);
				if (null == parameter) {
					throw new InvalidNameException(
							String.format(
									"the genericParam of annotation [%s] in method %s of %s not one of  method's parameter names",
									ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
											.getSimpleName()));
				} else if (parameter.type != getServiceInfo().getTargetType()) {
					throw new InvalidNameException(
							String.format(
									"the parameter's type specified genericParam of annotation [%s] in method %s of %s is not target type defined in %s of class %s",
									ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
											.getSimpleName(), Service.class.getSimpleName(), interfaceClass
											.getSimpleName()));
				}
			}
			Set<String> typeSet=CodeGenUtils.toSetIfnotDup(servicePort.genericTypes());
			if(null==typeSet)
				throw new InvalidNameException(
						String.format("the genericTypes of annotation [%s] in method %s of %s must not have duplicated element",
								ServicePort.class.getSimpleName(), method.getName(), method.getDeclaringClass()
										.getSimpleName()));
			
			typeSet.removeAll(getServiceInfo().getGenericTypeMap().keySet());
			if (!typeSet.isEmpty())
				throw new InvalidNameException(String.format(
						"the elements of genericTypes of annotation [%s] in method %s of %s must defined in %s",
						ServicePort.class.getSimpleName(), method.getName(),
						method.getDeclaringClass().getSimpleName(), Service.class.getSimpleName()));
		}
	}

	@Override
	protected void createMethodsNeedGenerated() {
		ArrayList<java.lang.reflect.Method> interfaceMethods = new ArrayList<java.lang.reflect.Method>(
				Arrays.asList(interfaceClass.getMethods()));
		java.lang.reflect.Method[] refMethods = refClass.getDeclaredMethods();
		java.lang.reflect.Method[] superMethods = refClass.getSuperclass().getMethods();
		java.lang.reflect.Method rm,bm;
		Iterator<java.lang.reflect.Method> it = interfaceMethods.iterator();
		try {
			while (it.hasNext()) {
				java.lang.reflect.Method im = it.next();
				if (null != im.getAnnotation(ServicePort.class)) {
					this.methodsNeedGenerated.add(new Method(im, this.paramTable.getParameterNames(im.getName(),
							im.getParameterTypes())));
				} else if (null==(bm=getImplementedMethod(superMethods, im))) {// baseClass没有实现接口方法					
					rm = getImplementedMethod(refMethods, im);
					assert rm!=null;
					this.methodsNeedGenerated.add(new Method(rm, this.paramTable.getParameterNames(im.getName(),
							im.getParameterTypes())));
				}else if(null != bm.getAnnotation(ServicePort.class)){
					this.methodsNeedGenerated.add(new Method(bm, this.paramTable.getParameterNames(im.getName(),
							im.getParameterTypes())));					
				}
			}
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}

	}

	public final ServiceInfo getServiceInfo() {
		return serviceInfo;
	}

	public final ServicePort getServicePort(Method method) {
		return method.getAnnotation(ServicePort.class);
	}

	public final Method getMatchedGenericMethod(Class<?> clazz, Method method, Class<?> expectedType)
			throws MethodException {
		ServicePort servicePort = method.getAnnotation(ServicePort.class);
		if (servicePort == null)
			throw new MethodException(String.format("not defined annotation %s in method %s ",
					ServicePort.class.getSimpleName(), method.getDocSignature(false)));
		return getMatchedGenericMethod((null==clazz)?interfaceClass:clazz, method, expectedType, servicePort.genericParam(), getServiceInfo()
				.getTargetType());
	}
	public final Method getMatchedGenericMethodFromInterface(Method method, Class<?> expectedType)
			throws MethodException {
		return getMatchedGenericMethod(interfaceClass,method,expectedType);
	}
	@SuppressWarnings("unchecked")
	public final List<Class<? extends Throwable>> getThrowsByMatchedGenericMethod(Class<?> clazz, Method method,
			Class<?> expectedType) throws MethodException {		
		Set<Class<?>> srcSet = CodeGenUtils.toSet(getMatchedGenericMethod(clazz, method, expectedType)
				.getExceptionTypes());
		Set<Class<?>> retain = new HashSet<Class<?>>(srcSet);
		retain.retainAll(CodeGenUtils.toSet(method.getExceptionTypes()));
		srcSet.removeAll(retain);
		Collection<Class<? extends Throwable>> res = new ArrayList<Class<? extends Throwable>>();
		for (Iterator<Class<?>> it = srcSet.iterator(); it.hasNext();) {
			res.add((Class<? extends Throwable>) it.next());
		}
		return GeneratorUtils.sortAndFilterThrowable(res,RuntimeException.class);
	}
	public final List<Class<? extends Throwable>> getThrowsByMatchedGenericMethod(Method method,
			Class<?> expectedType) throws MethodException {
		return getThrowsByMatchedGenericMethod(interfaceClass,method,expectedType);
	}
}
